<?php
/**
 * Dirox Framework
 * Copyright (C) 2005 - 2007 Dirox / Synexser
 * 
 * All rights reserved.
 */
 
require_once 'DB/DataObject.php';

class DRX_DataObject extends DB_DataObject
{
	private $error = 0;
	
	function _connect()
	{
		global $_DB_DATAOBJECT;
		if (!$this->_database_dsn_md5 || empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]))
		{
			if (($result = parent::_connect()) && ACTIVATE_UTF8_SUPPORT)
			{
				// Connection was ok, we can issue a SET NAMES query for i18n
				$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->query("SET NAMES 'utf8';");
				// Set time_zone using PHP to benefit of application timezone config
				$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->query("SET time_zone = '".date('P')."'");
			}
			return $result;
		}
		else
		{
			return parent::_connect();
		}
	}

	public function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false, $joinCond = null)
	{
		parent::joinAdd($obj, $joinType, $joinAs, $joinCol);
		
		if (isset($joinCond)) {
			preg_match('/JOIN(.*)/', $this->_join, $result);
			if (strpos($result, $obj->_database.".".$obj->__table.".") === false) {
				$this->_join .= "AND " . $joinCond;
			} else {
				$this->_join = preg_replace('/JOIN(.*)/', 'JOIN\1 AND ' . $joinCond, $this->_join, 1);
			}
			
		}
	}
	
	public function fetchList()
	{
		$this->find();
		
		$ret = array();
		
		while($this->fetch()) {
			$ret[] = $this->toArray();
		}
		
		return $ret;
	}

	public function fetchListObject()
	{
		$this->find();

		$ret = array();

		while($this->fetch()) {
			$ret[$this->ID] = clone $this;
		}

		return $ret;
	}

	public function getSelectSql()
	{
		global $_DB_DATAOBJECT;
        if (!isset($this->_query)) {
            $this->raiseError(
                "You cannot do two queries on the same object (copy it before finding)",
                DB_DATAOBJECT_ERROR_INVALIDARGS);
            return false;
        }

        if (empty($_DB_DATAOBJECT['CONFIG'])) {
            DB_DataObject::_loadConfig();
        }

        if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
            $this->debug($n, "find",1);
        }
        if (!$this->__table) {
            // xdebug can backtrace this!
            trigger_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR);
        }
        $this->N = 0;
        $query_before = $this->_query;
        $this->_build_condition($this->table()) ;

        $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
        $this->_connect();
        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];

        /* We are checking for method modifyLimitQuery as it is PEAR DB specific */
        $sql = 'SELECT ' .
            $this->_query['data_select'] . " \n" .
            ' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" .
            $this->_join . " \n" .
            $this->_query['condition'] . " \n" .
            $this->_query['group_by']  . " \n" .
            $this->_query['having']    . " \n" .
            $this->_query['order_by']  . " \n";

        if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) ||
            ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
            /* PEAR DB specific */

            if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
                $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
            }
        } else {
            /* theoretically MDB2! */
            if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
	            $DB->setLimit($this->_query['limit_count'],$this->_query['limit_start']);
	        }
        }

		return $sql;
	}

	public function getInsertSql()
	{
		global $_DB_DATAOBJECT;

        // we need to write to the connection (For nextid) - so us the real
        // one not, a copyied on (as ret-by-ref fails with overload!)

        if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
            $this->_connect();
        }

        $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);

        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];

        $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?
            $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();

        if (!$items) {
            $this->raiseError("insert:No table definition for {$this->__table}",
                DB_DATAOBJECT_ERROR_INVALIDCONFIG);
            return false;
        }
        $options = &$_DB_DATAOBJECT['CONFIG'];


        $datasaved = 1;
        $leftq     = '';
        $rightq    = '';

        $seqKeys   = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]) ?
                        $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] :
                        $this->sequenceKey();

        $key       = isset($seqKeys[0]) ? $seqKeys[0] : false;
        $useNative = isset($seqKeys[1]) ? $seqKeys[1] : false;
        $seq       = isset($seqKeys[2]) ? $seqKeys[2] : false;

        $dbtype    = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"];


        // nativeSequences or Sequences..

        // big check for using sequences

        if (($key !== false) && !$useNative) {

            if (!$seq) {
                $keyvalue =  $DB->nextId($this->__table);
            } else {
                $f = $DB->getOption('seqname_format');
                $DB->setOption('seqname_format','%s');
                $keyvalue =  $DB->nextId($seq);
                $DB->setOption('seqname_format',$f);
            }
            if (PEAR::isError($keyvalue)) {
                $this->raiseError($keyvalue->toString(), DB_DATAOBJECT_ERROR_INVALIDCONFIG);
                return false;
            }
            $this->$key = $keyvalue;
        }



        foreach($items as $k => $v) {

            // if we are using autoincrement - skip the column...
            if ($key && ($k == $key) && $useNative) {
                continue;
            }


            if (!isset($this->$k)) {
                continue;
            }
            // dont insert data into mysql timestamps
            // use query() if you really want to do this!!!!
            if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
                continue;
            }

            if ($leftq) {
                $leftq  .= ', ';
                $rightq .= ', ';
            }

            $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ')  : "$k ");

            if (is_a($this->$k,'DB_DataObject_Cast')) {
                $value = $this->$k->toString($v,$DB);
                if (PEAR::isError($value)) {
                    $this->raiseError($value->toString() ,DB_DATAOBJECT_ERROR_INVALIDARGS);
                    return false;
                }
                $rightq .=  $value;
                continue;
            }



            if (is_string($this->$k) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
                $rightq .= " NULL ";
                continue;
            }
            // DATE is empty... on a col. that can be null..
            // note: this may be usefull for time as well..
            if (!$this->$k &&
                    (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) &&
                    !($v & DB_DATAOBJECT_NOTNULL)) {

                $rightq .= " NULL ";
                continue;
            }


            if ($v & DB_DATAOBJECT_STR) {
                $rightq .= $this->_quote((string) (
                        ($v & DB_DATAOBJECT_BOOL) ?
                            // this is thanks to the braindead idea of postgres to
                            // use t/f for boolean.
                            (($this->$k == 'f') ? 0 : (int)(bool) $this->$k) :
                            $this->$k
                    )) . " ";
                continue;
            }
            if (is_numeric($this->$k)) {
                $rightq .=" {$this->$k} ";
                continue;
            }
            /* flag up string values - only at debug level... !!!??? */
            if (is_object($this->$k) || is_array($this->$k)) {
                $this->debug('ODD DATA: ' .$k . ' ' .  print_r($this->$k,true),'ERROR');
            }

            // at present we only cast to integers
            // - V2 may store additional data about float/int
            $rightq .= ' ' . intval($this->$k) . ' ';

        }

        // not sure why we let empty insert here.. - I guess to generate a blank row..


        if ($leftq || $useNative) {
            $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table)    : $this->__table);
        }
        
        return "INSERT INTO {$table} ($leftq) VALUES ($rightq)";
	}

	public function getUpdateSql()
	{
		global $_DB_DATAOBJECT;
        // connect will load the config!
        $this->_connect();


        $original_query = isset($this->_query) ? $this->_query : null;

        $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?
            $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();

        // only apply update against sequence key if it is set?????

        $seq    = $this->sequenceKey();
        if ($seq[0] !== false) {
            $keys = array($seq[0]);
            if (empty($this->{$keys[0]}) && $dataObject !== true) {
                $this->raiseError("update: trying to perform an update without
                        the key set, and argument to update is not
                        DB_DATAOBJECT_WHEREADD_ONLY
                    ", DB_DATAOBJECT_ERROR_INVALIDARGS);
                return false;
            }
        } else {
            $keys = $this->keys();
        }


        if (!$items) {
            $this->raiseError("update:No table definition for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
            return false;
        }
        $datasaved = 1;
        $settings  = '';
        $this->_connect();

        $DB            = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
        $dbtype        = $DB->dsn["phptype"];
        $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);

        foreach($items as $k => $v) {
            if (!isset($this->$k)) {
                continue;
            }
            // ignore stuff thats

            // dont write things that havent changed..
            if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k === $this->$k)) {
                continue;
            }

            // - dont write keys to left.!!!
            if (in_array($k,$keys)) {
                continue;
            }

             // dont insert data into mysql timestamps
            // use query() if you really want to do this!!!!
            if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
                continue;
            }


            if ($settings)  {
                $settings .= ', ';
            }

            $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);

            if (is_a($this->$k,'DB_DataObject_Cast')) {
                $value = $this->$k->toString($v,$DB);
                if (PEAR::isError($value)) {
                    $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
                    return false;
                }
                $settings .= "$kSql = $value ";
                continue;
            }

            // special values ... at least null is handled...
            if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
                $settings .= "$kSql = NULL ";
                continue;
            }
            // DATE is empty... on a col. that can be null..
            // note: this may be usefull for time as well..
            if (!$this->$k &&
                    (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) &&
                    !($v & DB_DATAOBJECT_NOTNULL)) {

                $settings .= "$kSql = NULL ";
                continue;
            }


            if ($v & DB_DATAOBJECT_STR) {
                $settings .= "$kSql = ". $this->_quote((string) (
                        ($v & DB_DATAOBJECT_BOOL) ?
                            // this is thanks to the braindead idea of postgres to
                            // use t/f for boolean.
                            (($this->$k == 'f') ? 0 : (int)(bool) $this->$k) :
                            $this->$k
                    )) . ' ';
                continue;
            }
            if (is_numeric($this->$k)) {
                $settings .= "$kSql = {$this->$k} ";
                continue;
            }
            // at present we only cast to integers
            // - V2 may store additional data about float/int
            $settings .= "$kSql = " . intval($this->$k) . ' ';
        }


        if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
            $this->debug("got keys as ".serialize($keys),3);
        }
        if ($dataObject !== true) {
            $this->_build_condition($items,$keys);
        } else {
            // prevent wiping out of data!
            if (empty($this->_query['condition'])) {
                 $this->raiseError("update: global table update not available
                        do \$do->whereAdd('1=1'); if you really want to do that.
                    ", DB_DATAOBJECT_ERROR_INVALIDARGS);
                return false;
            }
        }



        //  echo " $settings, $this->condition ";
        if ($settings && isset($this->_query) && $this->_query['condition']) {
            $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
        }
        
        return "UPDATE  {$table}  SET {$settings} {$this->_query['condition']}";
	}

	public function getDeleteSql()
	{
		 global $_DB_DATAOBJECT;
        // connect will load the config!
        $this->_connect();
        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
        $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);

        $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : '');

        if (!$useWhere) {

            $keys = $this->keys();
            $this->_query = array(); // as it's probably unset!
            $this->_query['condition'] = ''; // default behaviour not to use where condition
            $this->_build_condition($this->table(),$keys);
            // if primary keys are not set then use data from rest of object.
            if (!$this->_query['condition']) {
                $this->_build_condition($this->table(),array(),$keys);
            }
            $extra_cond = '';
        }


        // don't delete without a condition
        if (isset($this->_query) && $this->_query['condition']) {

            $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
            $sql = "DELETE FROM {$table} {$this->_query['condition']}{$extra_cond}";

            // add limit..

            if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {

                if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) ||
                    ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
                    // pear DB
                    $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);

                } else {
                    // MDB2
                    $DB->setLimit( $this->_query['limit_count'],$this->_query['limit_start']);
                }
            }
        }
        
        return $sql;
	}
	
	public function query($sql) {
		$ret = parent::query($sql);
		
		if ($ret instanceof DB_Error) {
			$this->error++;
		}
		
		return $ret;
	}

	public function insert() {
		$ret = parent::insert();

		if ($ret === false) {
			$this->error++;
		}

		return $ret;
	}
	
	public function update($dataObject = false) {
		$ret = parent::update($dataObject);
		
		if ($ret === false) {
			$this->error++;
		}
		
		return $ret;
	}

	public function save() {
		if (!$this->ID) {
			return $this->insert();
		} else {
			return $this->update();
		}
	}

	public function delete($useWhere = false) {
		$ret = parent::delete($useWhere);
		
		if ($ret === false) {
			$this->error++;
		}
		
		return $ret;
	}
	
	public function startTransaction() {
		$this->query('BEGIN');
	}
	
	public function commitTransaction() {
		$this->query('COMMIT');
	}
	
	public function rollbackTransaction() {
		$this->query('ROLLBACK');
	}
	
	public function hasError() {
		return $this->error > 0;
	}
}
